<?php

namespace Search\SearchBaidu;

use Search\SearchTool;
use Search\SearchResult;
use DOMDocument;
use DOMNode;
use DOMElement;

final class SearchBaidu implements SearchTool
{
    /**
     * @param string $keywords
     * @return SearchResult[]
     */
    public function search(string $keywords): array
    {
        $url = $this->getQueryUrl($keywords);
        return $this->htmlTextToResult($this->doQueryCommand($url));
    }

    private function getQueryUrl(string $keywords): string
    {
        $encoded = urlencode($keywords);
        return "https://www.baidu.com/s?wd=$encoded";
    }

    /**
     * 利用curl扩展查询。
     *
     * 经过测试，不知道哪里有问题，所以没用此方法。
     * @param string $url
     * @return string
     */
    private function doQuery(string $url): string
    {
        $handle = curl_init();
        if (false === $handle) {
            throw new SearchBaiduException('`curl_init` fail!');
        }
        $headers = [
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
        ];
        if (false === curl_setopt_array($handle, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',
                CURLOPT_URL => $url,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_ENCODING => '',
                CURLOPT_COOKIEFILE => 'cookie.txt',
                CURLOPT_COOKIEJAR => 'cookie.txt',
                CURLOPT_HTTPHEADER => $headers,
            ])
        ) {
            throw new SearchBaiduException('`curl_setopt_array` fail!');
        }

        $result = curl_exec($handle);
        if (false === $result) {
            throw new SearchBaiduException('`curl_exec` fail: ' . curl_error($handle));
        }

        curl_close($handle);
        return $result;
    }

    private function doQueryCommand(string $url): string
    {
        $command = [
            'curl',
            '-H', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
            '-H', 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
            '-H', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0',
            $url,
        ];
        $commandStdout = '';
        $commandErrout = '';
        $pipes = [];
        $handle = proc_open($command, [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ], $pipes);
        if (is_resource($handle)) {
            fwrite($pipes[0], '');
            fclose($pipes[0]);
            $commandStdout = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            $commandErrout = stream_get_contents($pipes[2]);
            fclose($pipes[2]);
            $returnValue = proc_close($handle);
        }
        return $commandStdout;
    }

    private function htmlTextToResult(string $htmlText): array
    {
        $doc = new DOMDocument();
        @$doc->loadHTML($htmlText);
        
        $list = $doc->getElementsByTagName('div');

        $result = [];
        foreach ($list as $item) {
            try {
                $result[] = $this->findResultFromDOM($item);
            } catch (NoSearchResultException $e) {
            }
        }
        return $result;
    }

    private function getClasses(DOMNode $item): array
    {
        $results = [];
        if ($item instanceof DOMElement) {
            $results = explode(' ', $item->getAttribute('class'));
        }
        return $results;
    }

    private function findResultFromDOM(DOMNode $item): SearchResult
    {
        foreach ($this->getClasses($item) as $classes) {
            if (0 === strpos($classes, '_content_')) {
                try {
                    $searchResult = $this->getSearchResultFromDOM($item);
                } catch (SearchBaiduException $e) {
                    throw new NoSearchResultException($e->getMessage());
                }
                return $searchResult;
            }
        }
        throw new NoSearchResultException('No SearchResult');
    }

    private function getSearchResultFromDOM(DOMElement $item): SearchResult
    {
        $titleList = $item->getElementsByTagName('h3');
        $title = '';
        if (0 != $titleList->length) {
            $title = trim(htmlspecialchars_decode($titleList->item(0)->textContent));
        }
        $richContentList = $item->getElementsByTagName('div');
        $content = '';
        foreach ($richContentList as $richContent) {
            if (in_array('cu-line-clamp-2', $this->getClasses($richContent))) {
                $content .= trim(htmlspecialchars_decode($richContent->textContent)) . PHP_EOL;
            }
        }
        return new SearchBaiduResult($title, trim($content));
    }
}
